本文讨论服务端自动刷新令牌过期时间的最佳实践。
场景引入
在使用
Token
机制进行Auth
时,出于安全性的考虑我们很容易想到需要为令牌设置过期时间,然而小菜如我往往也忽略了体验性的问题即“自动延长过期时间”以保持活跃用户在线,引用 Auth0 的一段话意会一哈——“一个好的模式是在它过期之前刷新令牌。将令牌过期时间设置为一周,并在每次用户打开Web应用程序并每隔一小时刷新令牌。如果用户超过一周没有打开过应用程序,那他们就需要再次登录,这是可接受的Web应用程序UX(用户体验)。要刷新令牌,API需要一个新的端点,它接收一个有效的,没有过期的JWT,并返回与新的到期字段相同的签名的JWT。然后Web应用程序会将令牌存储在某处。”
我们把问题拆分为两个维度:
- 单点登录:单点登录 (Single Sign On, SSO),在应用场景上分为同域单点登录和跨域单点登录,在实现方案上分为
OAuth
和CAS
,etc…单点登录也是一个很有趣的需求场景,以后有机会考虑开专栏讨论下个人的一些浅见,现在假设我们已经站在巨人的肩膀上,Token
加密内容的生成和验证将作为先验知识不会在本文中过多地解释。- 令牌刷新:本文重点在于思考“如何优雅地设置
Token
有效时间”。在真正动手前需要考量几个问题,并且要十分注意实际生产中容易踩到的坑,回到算法本身而言寻求从静态到动态的优化与提升。
灵魂三问
主体是谁?
令牌刷新的主体,可以是服务端,可以是客户端,怎么做选择题Ⅰ?
- 服务端:控制令牌信息更灵活,实现较简单
- 客户端:控制交互体验更灵活,实现较繁琐
客体是谁?
令牌刷新的客体,可以是加密内容,可以是过期时间,怎么做选择题Ⅱ?
- 加密内容:优点是可以使用
JWT
而无需存储;缺点是频繁生成新的令牌浪费空间,维护多个令牌容易出现安全问题和并发问题,常见解决方案有refresh token
方案和middleware
方案。 - 过期时间:优点是可以使用
Random Ticket
类似于“单例”而没有空间、安全、并发等的问题;缺点是需要维护额外的数据库存储,一般来说考虑到令牌将被热点访问和定期删除,Redis
是最佳的实践,使用MySQL / MongoDB
在单机和低并发的情况下也是可行的。
在什么条件下?
- < 临界过期时间:无需登录,令牌不会刷新
- 临界过期时间 ~ 预设过期时间:无需登录,令牌自动刷新
- > 预设过期时间:需要登录,新的令牌周期
具体实现
综合以上,下面提供服务端自动刷新令牌过期时间的简要解决方案。
静态刷新
- 优点:实现方式简单粗暴,每验证一次就更新一次。
- 缺点:更新太过频繁,性能受损,令牌可能出现“几乎不会过期”有悖设计初衷。
动态刷新
- 优点:设置过期临界,对更新的条件做了限制,提升性能,使用合理。
- 缺点:维护两个过期时间——临界过期时间和预设过期时间。
踩坑警告
- 注意重置密码时同步地处理令牌。
- 注意浏览器多选项卡并发操作的影响。
- 注意过期令牌妥善处理,可以放着不管 or 定期删除,但是不能直接覆盖(👉#Microservice# Golang Microservice 工程开发踩坑记录👈)
参考链接
- JWT(JSON Web Token)自动延长到期时间
- 对于token的认证,如何保证token的及时刷新?
- Shiro整合JWT+Token过期刷新,全都帮你整好了
- 采用JWT有效期内刷新Token方案,解决并发请求问题
- Token 过期的问题